<?php

namespace Deployer;

// Used to make a writable directory by a server.
// Used in `chown` and `acl` modes of {{writable_mode}}.
// Attempts automatically to detect http user in process list.

set('http_user', function () {
    $candidates = explode("\n", run("ps axo comm,user | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | sort | awk '{print \$NF}' | uniq"));
    $httpUser = array_shift($candidates);

    if (empty($httpUser)) {
        throw new \RuntimeException(
            "Can't detect http user name.\n" .
            "Please setup `http_user` config parameter.",
        );
    }

    return $httpUser;
});

// Used to make a writable directory by a server.
// Used in `chgrp` mode of {{writable_mode}} only.
// Attempts automatically to detect http user in process list.
set('http_group', function () {
    $candidates = explode("\n", run("ps axo comm,group | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | sort | awk '{print \$NF}' | uniq"));
    $httpGroup = array_shift($candidates);

    if (empty($httpGroup)) {
        throw new \RuntimeException(
            "Can't detect http user name.\n" .
            "Please setup `http_group` config parameter.",
        );
    }

    return $httpGroup;
});

// List of writable dirs.
set('writable_dirs', []);

// One of:
// - chown
// - chgrp
// - chmod
// - acl
// - sticky
// - skip
set('writable_mode', 'acl');

// Using sudo in writable commands?
set('writable_use_sudo', false);

// Use recursive mode (-R)?
set('writable_recursive', false);

// The chmod mode.
set('writable_chmod_mode', '0755');

// List of additional groups to give write permission to.
set('writable_acl_groups', []);

// Force ACLs to be reapplied even if they already exist. Useful when recursive ACLs need to reach new nested paths but sudo isn't available. Slower, so enable only to fix writable dir permissions.
set('writable_acl_force', false);

desc('Makes writable dirs');
task('deploy:writable', function () {
    $dirs = join(' ', get('writable_dirs'));
    $mode = get('writable_mode');
    $recursive = get('writable_recursive') ? '-R' : '';
    $sudo = get('writable_use_sudo') ? 'sudo' : '';

    if (empty($dirs)) {
        return;
    }
    // Check that we don't have absolute path
    if (strpos($dirs, ' /') !== false) {
        throw new \RuntimeException('Absolute path not allowed in config parameter `writable_dirs`.');
    }

    cd('{{release_or_current_path}}');

    // Create directories if they don't exist
    run("mkdir -p $dirs");

    if ($mode === 'chown') {
        $httpUser = get('http_user');
        // Change owner.
        // -L   traverse every symbolic link to a directory encountered
        run("$sudo chown -L $recursive $httpUser $dirs");
    } elseif ($mode === 'chgrp') {
        // Change group ownership.
        // -L    traverse every symbolic link to a directory encountered
        run("$sudo chgrp -L $recursive {{http_group}} $dirs");
        run("$sudo chmod $recursive g+rwx $dirs");
    } elseif ($mode === 'chmod') {
        run("$sudo chmod $recursive {{writable_chmod_mode}} $dirs");
    } elseif ($mode === 'acl') {
        $remoteUser = get('remote_user', false);
        if (empty($remoteUser)) {
            $remoteUser = run('whoami');
        }
        $httpUser = get('http_user');
        if (run("uname -s") === 'Darwin') {
            // macOS supports chmod +a for ACL management

            run("$sudo chmod +a \"$httpUser allow delete,write,append,file_inherit,directory_inherit\" $dirs");
            run("$sudo chmod +a \"$remoteUser allow delete,write,append,file_inherit,directory_inherit\" $dirs");
        } elseif (commandExist('setfacl')) {
            $setFaclUsers = "-m u:\"$httpUser\":rwX";
            $setFaclGroups = "";
            foreach (get("writable_acl_groups") as $index => $group) {
                if ($index > 0) {
                    $setFaclGroups .= " ";
                }
                $setFaclGroups .= "-m g:\"$group\":rwX";
            }
            // Check if remote user exists, before adding it to setfacl
            $remoteUserExists = test("id -u $remoteUser &>/dev/null 2>&1 || exit 0");
            if ($remoteUserExists === true) {
                $setFaclUsers .= " -m u:$remoteUser:rwX";
            }
            if (empty($sudo)) {
                // When running without sudo, exception may be thrown
                // if executing setfacl on files created by http user (in directory that has been setfacl before).
                // These directories/files should be skipped unless forcing ACL reset.
                // Now, we will check each directory for ACL and only setfacl for which has not been set before,
                // unless writable_acl_force is enabled.
                $writeableDirs = get('writable_dirs');
                $forceAcl = get('writable_acl_force');
                foreach ($writeableDirs as $dir) {
                    // Check if ACL has been set or not
                    $hasfacl = run("getfacl -p $dir | grep \"^user:$httpUser:.*w\" | wc -l");
                    // Set ACL for directory if it has not been set before or if forcing ACL reset
                    if ($forceAcl || !$hasfacl) {
                        run("setfacl -L $recursive $setFaclUsers $setFaclGroups $dir");
                        run("setfacl -dL $recursive $setFaclUsers $setFaclGroups $dir");
                    }
                }
            } else {
                run("$sudo setfacl -L $recursive $setFaclUsers $setFaclGroups $dirs");
                run("$sudo setfacl -dL $recursive $setFaclUsers $setFaclGroups $dirs");
            }
        } else {
            $alias = currentHost()->getAlias();
            throw new \RuntimeException("Can't set writable dirs with ACL.\nInstall ACL with next command:\ndep run 'sudo apt-get install acl' -- $alias");
        }
    } elseif ($mode === 'sticky') {
        // Changes the group of the files, sets sticky bit to the directories
        // and add the writable bit for all files
        run("for dir in $dirs;" .
            'do ' .
            'chgrp -L -R {{http_group}} ${dir}; ' .
            'find ${dir} -type d -exec chmod g+rwxs \{\} \;;' .
            'find ${dir} -type f -exec chmod g+rw \{\} \;;' .
            'done');
    } elseif ($mode === 'skip') {
        // Does nothing, saves time if no changes are required for some environments
        return;
    } else {
        throw new \RuntimeException("Unknown writable_mode `$mode`.");
    }
});
